/**
 * Copyright 2009-2011 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.myjung.interceptor;

import java.lang.reflect.Method;
import java.util.Map;

import net.myjung.Myjung;
import net.myjung.model.AdviceParameters;
import net.myjung.model.Query;
import net.myjung.model.CreateObjectParameters;
import net.myjung.model.DeleteObjectListParameters;
import net.myjung.model.DeleteObjectParameters;
import net.myjung.model.GetObjectListParameters;
import net.myjung.model.GetObjectParameters;
import net.myjung.model.GetObjectSizeParameters;
import net.myjung.model.SetObjectParameters;
import net.myjung.model.UpdateObjectParameters;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Steve M. Jung
 */
public abstract class AbstractManagerInterceptor implements MethodInterceptor {
	protected final Log logger = LogFactory.getLog(getClass());

	private static final String BEFORE = "before";
	private static final String AFTER = "after";
	private static final String THROWS = "throws";

	private static final String GET = "get";
	private static final String SET = "set";
	private static final String CREATE = "create";
	private static final String UPDATE = "update";
	private static final String DELETE = "delete";
	private static final String GETLIST = "getList";
	private static final String GETSIZE = "getSize";
	private static final String DELETELIST = "deleteList";

	private Method getMethod(String name) {
		Method[] methods = getClass().getMethods();
		if (!Myjung.isEmpty(methods)) {
			for (Method method : methods) {
				if (method.getName().equals(name))
					return method;
			}
		}
		return null;
	}
	@SuppressWarnings("unchecked")
	public Object invoke(MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		String methodName = method.getName();
		Object[] args = invocation.getArguments();
		try {
			String upperMethodName = Myjung.toUpperCase(methodName, 0);
			int i = 0;

			Object res = null;
			boolean cached = false;

			Method beforeAdvice = getMethod(new StringBuffer(BEFORE).append(upperMethodName).toString());
			if (beforeAdvice != null) {
				Class<?>[] paramTypes = beforeAdvice.getParameterTypes();
				String methodCategory = null;
				if (!Myjung.isEmpty(args)) {
					if (methodName.startsWith(GET)) {
						if (args.length == 2 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1) {
							if (paramTypes[0] == GetObjectParameters.class) {
								methodCategory = GET;

								i = 0;
								GetObjectParameters params = new GetObjectParameters((String) args[i++], (Map<String, Object>) args[i++]);
								beforeAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getId();
								args[i++] = params.getOptions();
								res = params.getResult();
								cached = params.isCached();
							} else if (paramTypes[0] == GetObjectSizeParameters.class) {
								methodCategory = GETSIZE;

								i = 0;
								GetObjectSizeParameters params = new GetObjectSizeParameters((Query) args[i++], (Map<String, Object>) args[i++]);
								beforeAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getConds();
								args[i++] = params.getOptions();
								res = params.getResult();
								cached = params.isCached();
							} else if (paramTypes[0] == GetObjectListParameters.class) {
								methodCategory = GETLIST;

								i = 0;
								GetObjectListParameters params = new GetObjectListParameters((Query) args[i++], (Map<String, Object>) args[i++]);
								beforeAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getConds();
								args[i++] = params.getOptions();
								res = params.getResult();
								cached = params.isCached();
							}
						}
					} else if (methodName.startsWith(SET)) {
						if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1 && paramTypes[0] == SetObjectParameters.class) {
							methodCategory = SET;

							i = 0;
							SetObjectParameters params = new SetObjectParameters((String) args[i++], args[i++], (Map<String, Object>) args[i++]);
							beforeAdvice.invoke(this, params);

							i = 0;
							args[i++] = params.getUser();
							args[i++] = params.getObject();
							args[i++] = params.getOptions();
							res = params.getResult();
							cached = params.isCached();
						}
					} else if (methodName.startsWith(CREATE)) {
						if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1
								&& paramTypes[0] == CreateObjectParameters.class) {
							methodCategory = CREATE;

							i = 0;
							CreateObjectParameters params = new CreateObjectParameters((String) args[i++], args[i++], (Map<String, Object>) args[i++]);
							beforeAdvice.invoke(this, params);

							i = 0;
							args[i++] = params.getUser();
							args[i++] = params.getObject();
							args[i++] = params.getOptions();
							res = params.getResult();
							cached = params.isCached();
						}
					} else if (methodName.startsWith(UPDATE)) {
						if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1
								&& paramTypes[0] == UpdateObjectParameters.class) {
							methodCategory = UPDATE;

							i = 0;
							UpdateObjectParameters params = new UpdateObjectParameters((String) args[i++], args[i++], (Map<String, Object>) args[i++]);
							beforeAdvice.invoke(this, params);

							i = 0;
							args[i++] = params.getUser();
							args[i++] = params.getObject();
							args[i++] = params.getOptions();
							res = params.getResult();
							cached = params.isCached();
						}
					} else if (methodName.startsWith(DELETE)) {
						if (args.length == 2 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1) {
							if (paramTypes[0] == DeleteObjectParameters.class) {
								methodCategory = DELETE;

								i = 0;
								DeleteObjectParameters params = new DeleteObjectParameters((String) args[i++], (Map<String, Object>) args[i++]);
								beforeAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getId();
								args[i++] = params.getOptions();
								res = params.getResult();
								cached = params.isCached();
							} else if (paramTypes[0] == DeleteObjectListParameters.class) {
								methodCategory = DELETELIST;

								i = 0;
								DeleteObjectListParameters params = new DeleteObjectListParameters((Query) args[i++], (Map<String, Object>) args[i++]);
								beforeAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getConds();
								args[i++] = params.getOptions();
								res = params.getResult();
								cached = params.isCached();
							}
						}
					}
				}

				if (methodCategory == null && paramTypes.length == 1 && paramTypes[0].isAssignableFrom(AdviceParameters.class)) {
					AdviceParameters params = new AdviceParameters(args);
					beforeAdvice.invoke(this, params);
					if (!Myjung.isEmpty(args)) {
						Object[] newArgs = params.getArgs();
						if (!Myjung.isEmpty(newArgs) && args.length == newArgs.length) {
							int j = 0;
							for (Object arg : newArgs)
								args[j++] = arg;
						}
					}
					res = params.getResult();
					cached = params.isCached();
				}
			}
			try {
				if (res == null)
					res = invocation.proceed();
			} catch (Throwable t) {
				Method throwsAdvice = getMethod(new StringBuffer(THROWS).append(upperMethodName).toString());
				if (throwsAdvice != null) {
					Class<?>[] paramTypes = throwsAdvice.getParameterTypes();
					String methodCategory = null;
					if (!Myjung.isEmpty(args)) {
						if (methodName.startsWith(GET)) {
							if (args.length == 2 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1) {
								if (paramTypes[0] == GetObjectParameters.class) {
									methodCategory = GET;

									i = 0;
									GetObjectParameters params = new GetObjectParameters((String) args[i++], (Map<String, Object>) args[i++], res, t);
									params.setCached(cached);
									throwsAdvice.invoke(this, params);

									i = 0;
									args[i++] = params.getId();
									args[i++] = params.getOptions();
									res = params.getResult();
									t = params.getThrowable();
								} else if (paramTypes[0] == GetObjectSizeParameters.class) {
									methodCategory = GETSIZE;

									i = 0;
									GetObjectSizeParameters params = new GetObjectSizeParameters((Query) args[i++], (Map<String, Object>) args[i++],
											res, t);
									params.setCached(cached);
									throwsAdvice.invoke(this, params);

									i = 0;
									args[i++] = params.getConds();
									args[i++] = params.getOptions();
									res = params.getResult();
									t = params.getThrowable();
								} else if (paramTypes[0] == GetObjectListParameters.class) {
									methodCategory = GETLIST;

									i = 0;
									GetObjectListParameters params = new GetObjectListParameters((Query) args[i++], (Map<String, Object>) args[i++],
											res, t);
									params.setCached(cached);
									throwsAdvice.invoke(this, params);

									i = 0;
									args[i++] = params.getConds();
									args[i++] = params.getOptions();
									res = params.getResult();
									t = params.getThrowable();
								}
							}
						} else if (methodName.startsWith(SET)) {
							if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1
									&& paramTypes[0] == SetObjectParameters.class) {
								methodCategory = SET;

								i = 0;
								SetObjectParameters params = new SetObjectParameters((String) args[i++], args[i++], (Map<String, Object>) args[i++],
										res, t);
								params.setCached(cached);
								throwsAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getUser();
								args[i++] = params.getObject();
								args[i++] = params.getOptions();
								res = params.getResult();
								t = params.getThrowable();
							}
						} else if (methodName.startsWith(CREATE)) {
							if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1
									&& paramTypes[0] == CreateObjectParameters.class) {
								methodCategory = CREATE;

								i = 0;
								CreateObjectParameters params = new CreateObjectParameters((String) args[i++], args[i++],
										(Map<String, Object>) args[i++], res, t);
								params.setCached(cached);
								throwsAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getUser();
								args[i++] = params.getObject();
								args[i++] = params.getOptions();
								res = params.getResult();
								t = params.getThrowable();
							}
						} else if (methodName.startsWith(UPDATE)) {
							if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1
									&& paramTypes[0] == UpdateObjectParameters.class) {
								methodCategory = UPDATE;

								i = 0;
								UpdateObjectParameters params = new UpdateObjectParameters((String) args[i++], args[i++],
										(Map<String, Object>) args[i++], res, t);
								params.setCached(cached);
								throwsAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getUser();
								args[i++] = params.getObject();
								args[i++] = params.getOptions();
								res = params.getResult();
								t = params.getThrowable();
							}
						} else if (methodName.startsWith(DELETE)) {
							if (args.length == 2 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1) {
								if (paramTypes[0] == DeleteObjectParameters.class) {
									methodCategory = DELETE;

									i = 0;
									DeleteObjectParameters params = new DeleteObjectParameters((String) args[i++], (Map<String, Object>) args[i++],
											res, t);
									params.setCached(cached);
									throwsAdvice.invoke(this, params);

									i = 0;
									args[i++] = params.getId();
									args[i++] = params.getOptions();
									res = params.getResult();
									t = params.getThrowable();
								} else if (paramTypes[0] == DeleteObjectListParameters.class) {
									methodCategory = DELETELIST;

									i = 0;
									DeleteObjectListParameters params = new DeleteObjectListParameters((Query) args[i++],
											(Map<String, Object>) args[i++], t);
									params.setCached(cached);
									throwsAdvice.invoke(this, params);

									i = 0;
									args[i++] = params.getConds();
									args[i++] = params.getOptions();
									t = params.getThrowable();
								}
							}
						}
					}

					if (methodCategory == null && paramTypes.length == 1 && paramTypes[0].isAssignableFrom(AdviceParameters.class)) {
						AdviceParameters params = new AdviceParameters(args, res, t);
						params.setCached(cached);
						throwsAdvice.invoke(this, params);
						if (!Myjung.isEmpty(args)) {
							Object[] newArgs = params.getArgs();
							if (!Myjung.isEmpty(newArgs) && args.length == newArgs.length) {
								int j = 0;
								for (Object arg : newArgs)
									args[j++] = arg;
							}
						}
						t = params.getThrowable();
					}
				}

				throw t;
			}

			Method afterAdvice = getMethod(new StringBuffer(AFTER).append(upperMethodName).toString());
			if (afterAdvice != null) {
				Class<?>[] paramTypes = afterAdvice.getParameterTypes();
				String methodCategory = null;
				if (!Myjung.isEmpty(args)) {
					if (methodName.startsWith(GET)) {
						if (args.length == 2 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1) {
							if (paramTypes[0] == GetObjectParameters.class) {
								methodCategory = GET;

								i = 0;
								GetObjectParameters params = new GetObjectParameters((String) args[i++], (Map<String, Object>) args[i++], res);
								params.setCached(cached);
								afterAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getId();
								args[i++] = params.getOptions();
								res = params.getResult();
							} else if (paramTypes[0] == GetObjectSizeParameters.class) {
								methodCategory = GETSIZE;

								i = 0;
								GetObjectSizeParameters params = new GetObjectSizeParameters((Query) args[i++], (Map<String, Object>) args[i++], res);
								params.setCached(cached);
								afterAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getConds();
								args[i++] = params.getOptions();
								res = params.getResult();
							} else if (paramTypes[0] == GetObjectListParameters.class) {
								methodCategory = GETLIST;

								i = 0;
								GetObjectListParameters params = new GetObjectListParameters((Query) args[i++], (Map<String, Object>) args[i++], res);
								params.setCached(cached);
								afterAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getConds();
								args[i++] = params.getOptions();
								res = params.getResult();
							}
						}
					} else if (methodName.startsWith(SET)) {
						if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1 && paramTypes[0] == SetObjectParameters.class) {
							methodCategory = SET;

							i = 0;
							SetObjectParameters params = new SetObjectParameters((String) args[i++], args[i++], (Map<String, Object>) args[i++], res);
							params.setCached(cached);
							afterAdvice.invoke(this, params);

							i = 0;
							args[i++] = params.getUser();
							args[i++] = params.getObject();
							args[i++] = params.getOptions();
							res = params.getResult();
						}
					} else if (methodName.startsWith(CREATE)) {
						if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1
								&& paramTypes[0] == CreateObjectParameters.class) {
							methodCategory = CREATE;

							i = 0;
							CreateObjectParameters params = new CreateObjectParameters((String) args[i++], args[i++],
									(Map<String, Object>) args[i++], res);
							params.setCached(cached);
							afterAdvice.invoke(this, params);

							i = 0;
							args[i++] = params.getUser();
							args[i++] = params.getObject();
							args[i++] = params.getOptions();
							res = params.getResult();
						}
					} else if (methodName.startsWith(UPDATE)) {
						if (args.length == 3 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1
								&& paramTypes[0] == UpdateObjectParameters.class) {
							methodCategory = UPDATE;

							i = 0;
							UpdateObjectParameters params = new UpdateObjectParameters((String) args[i++], args[i++],
									(Map<String, Object>) args[i++], res);
							params.setCached(cached);
							afterAdvice.invoke(this, params);

							i = 0;
							args[i++] = params.getUser();
							args[i++] = params.getObject();
							args[i++] = params.getOptions();
							res = params.getResult();
						}
					} else if (methodName.startsWith(DELETE)) {
						if (args.length == 2 && !Myjung.isEmpty(paramTypes) && paramTypes.length == 1) {
							if (paramTypes[0] == DeleteObjectParameters.class) {
								methodCategory = DELETE;

								i = 0;
								DeleteObjectParameters params = new DeleteObjectParameters((String) args[i++], (Map<String, Object>) args[i++], res);
								params.setCached(cached);
								afterAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getId();
								args[i++] = params.getOptions();
								res = params.getResult();
							} else if (paramTypes[0] == DeleteObjectListParameters.class) {
								methodCategory = DELETELIST;

								i = 0;
								DeleteObjectListParameters params = new DeleteObjectListParameters((Query) args[i++], (Map<String, Object>) args[i++]);
								params.setCached(cached);
								afterAdvice.invoke(this, params);

								i = 0;
								args[i++] = params.getConds();
								args[i++] = params.getOptions();
							}
						}
					}
				}

				if (methodCategory == null && paramTypes.length == 1 && paramTypes[0].isAssignableFrom(AdviceParameters.class)) {
					AdviceParameters params = new AdviceParameters(args, res);
					params.setCached(cached);
					afterAdvice.invoke(this, params);
					if (!Myjung.isEmpty(args)) {
						Object[] newArgs = params.getArgs();
						if (!Myjung.isEmpty(newArgs) && args.length == newArgs.length) {
							int j = 0;
							for (Object arg : newArgs)
								args[j++] = arg;
						}
					}
					res = params.getResult();
				}
			}

			return res;

		} catch (Throwable t) {
			throw t;
		}
	}
}
